/**
 * Drawer.java
 *
 * Created on Feb 6, 2007
 *
 * Copyright 2008 the Desert Labs Project (http://desertlabs.us)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package us.desertlabs.ui.drawer;

import java.awt.BorderLayout;
import java.beans.PropertyVetoException;
import javax.swing.JComponent;

/**
 * The <CODE>Drawer</CODE> is a special sort of container component that is meant to be positioned
 * along one of the edges of a parent view.  It displays a clickable "edge" that can be used to
 * open or close the <CODE>Drawer</CODE>, rapidly concealing or revealing its contents.  This
 * behavior is similar to that of a <CODE>JSplitPane</CODE>, but with some important differences:
 * <UL>
 * <LI>The <CODE>JSplitPane</CODE> can be resized to any width.  The <CODE>Drawer</CODE> is either
 * closed, or it is the size of its contents.
 * <LI>The <CODE>JSplitPane</CODE> can be one-touch expanded in either direction.  The <CODE>Drawer</CODE>
 * is either closed, which happens in only one direction.
 * <LI>The <CODE>JSplitPane</CODE> has tiny buttons on it.  The <CODE>Drawer</CODE>'s entire edge is
 * effectively a toggle button.
 * </UL>
 *
 * @author Brandon Franklin
 */
public class Drawer extends JComponent {

    static final private long serialVersionUID = 7562077134784173294L;

    /** The <CODE>DrawerEdge</CODE> used by this <CODE>Drawer</CODE> */
    final private DrawerEdge edge;

    /** The <CODE>JComponent</CODE> that is contained inside the <CODE>Drawer</CODE> */
    private JComponent contents = null;

    /** Whether or not the <CODE>Drawer</CODE> is currently open */
    private boolean open = false;

    /** The position of the <CODE>Drawer</CODE> inside its container */
    private DrawerOrientation orientation = DrawerOrientation.LEFT;

    /**
     * Default constructor. Creates all of the subcomponents involved in
     * representing a <CODE>Drawer</CODE> and adds them to the <CODE>Drawer</CODE>
     * as children.
     */
    public Drawer() {
        setLayout( new BorderLayout() );
        edge = new DrawerEdge( this );

        updateComponentPositions();
    }

    /**
     * Returns the position of the <CODE>Drawer</CODE> inside its
     * container. This only refers to the way that the <CODE>Drawer</CODE>
     * displays itself. It does not <I>necessarily</I> reflect the location
     * of the actual <CODE>Drawer</CODE> component in the container's
     * layout.
     *
     * @return the position of the <CODE>Drawer</CODE> inside its
     * container
     */
    public DrawerOrientation getOrientation() {
        return orientation;
    }

    /**
     * Returns whether or not the <CODE>Drawer</CODE> is currently open.
     *
     * @return true if the <CODE>Drawer</CODE> is open, false otherwise
     */
    public boolean isOpen() {
        return open;
    }

    /**
     * Sets the <CODE>JComponent</CODE> that will be shown inside this
     * <CODE>Drawer</CODE> when it is open.
     *
     * @param contents the <CODE>JComponent</CODE> that will be shown
     * inside this <CODE>Drawer</CODE> when it is open
     */
    public void setContents( final JComponent contents ) {
        final JComponent oldValue = contents;
        this.contents = contents;
        firePropertyChange( "open", oldValue, contents );

        // If the drawer is open, we need to update the contents
        if( open ) {
            remove( oldValue );
            add( contents, BorderLayout.CENTER );
        }
    }

    /**
     * Sets whether or not the <CODE>Drawer</CODE> is currently open.
     *
     * @param value true if the <CODE>Drawer</CODE> should be open,
     * false otherwise
     */
    public void setOpen( final boolean value ) {

        if( open == value ) {
            return;
        }

        final boolean oldValue = open;
        try {
            fireVetoableChange( "open", oldValue, value );
        } catch( final PropertyVetoException e ) {
            return;
        }

        open = value;
        if( contents != null ) {
            if( open ) {
                add( contents, BorderLayout.CENTER );
            } else {
                remove( contents );
            }
        }

        firePropertyChange( "open", oldValue, value );
    }

    /**
     * Sets the position of the <CODE>Drawer</CODE> inside its container.
     * This only affects the way that the <CODE>Drawer</CODE> displays
     * itself. It is still up to the user of the API to ensure that the
     * actual <CODE>Drawer</CODE> component is placed in the correct
     * portion of the container's layout.
     *
     * @param orientation the position of the <CODE>Drawer</CODE> inside its
     * container
     */
    public void setOrientation( final DrawerOrientation orientation ) {
        final DrawerOrientation oldValue = orientation;
        try {
            fireVetoableChange( "orientation", oldValue, orientation );
        } catch( final PropertyVetoException e ) {
            return;
        }
        this.orientation = orientation;
        firePropertyChange( "orientation", oldValue, orientation );

        updateComponentPositions();
    }

    /**
     * Uses the <CODE>Drawer</CODE>'s orientation to reposition all of
     * the constituent <CODE>Component</CODE>s used to render the <CODE>Drawer</CODE>.
     */
    private void updateComponentPositions() {
        remove( edge );

        switch( orientation ) {
            case LEFT:
                add( edge, BorderLayout.EAST );
                break;
            case RIGHT:
                add( edge, BorderLayout.WEST );
                break;
            case TOP:
                add( edge, BorderLayout.SOUTH );
                break;
            case BOTTOM:
                add( edge, BorderLayout.NORTH );
                break;
        }
    }
}
